/* To launch this program from within Mathematica use:
 *   In[1]:= link = Install["addtwo"]
 *
 * Or, launch this program from a shell and establish a
 * peer-to-peer connection.  When given the prompt Create Link:
 * type a port name. ( On Unix platforms, a port name is a
 * number less than 65536.  On Mac or Windows platforms,
 * it's an arbitrary word.)
 * Then, from within Mathematica use:
 *   In[1]:= link = Install["portname", LinkMode->Connect]
 */

// remember to include niqadmx.lib in the additional linker input additional dependances
// from examples the includes are:		odbc32.lib odbccp32.lib nidaqmx.lib

// note order of includes matters jobs before options bad
#include "getvoltages.h"
#include "job.h"
#include "VisaSerial.h"
#include <windows.h>  // just for timing

int terminalmodearray[]={DAQmx_Val_Cfg_Default,DAQmx_Val_RSE, \
		DAQmx_Val_NRSE,DAQmx_Val_Diff};
int unitsarray[]={DAQmx_Val_Volts,DAQmx_Val_FromCustomScale};
int activeedgearray[]={DAQmx_Val_Rising, DAQmx_Val_Falling};
int samplemodearray[]={DAQmx_Val_FiniteSamps,DAQmx_Val_ContSamps};
int transfermecharray[]={DAQmx_Val_DMA, DAQmx_Val_Interrupts, DAQmx_Val_ProgrammedIO};

// extern int outputvoltage(float64 volts);

persistJobHolder persistJobs;			// global class deffinition!!

bool sendErr = true;	// set to true to send errmsgs to mathematica

void serialportcommunicatecpp( void)
{
	option optTemp;
	optTemp.fromMathematica(stdlink);
	optTemp.getNIDAQConstants();
	sendmessage("Here are the options: \\\\n", optTemp.toString(), optTemp.L["messagelevel"]);

	Instrument in;
	in.setAttribute(VI_ATTR_TMO_VALUE, (long) 1000* optTemp.D["timeout"]);
	in.setAttribute(VI_ATTR_TERMCHAR, (optTemp.S["terminationChar"].c_str())[0]);
	if (optTemp.L["listenForReply"]) {
		in.write(optTemp.S["messageToSend"]);
		string reply = in.read(optTemp.L["charsToRead"], optTemp.L["timeoutIsErr"]);
		MLPutString(stdlink, reply.c_str());
	}
	else {
		int charsWritten = in.write(optTemp.S["messageToSend"]);
		MLPutLongInteger(stdlink, charsWritten);
	}
}

void runtask(void)
{
	const char		*ctemp;
	int			n1;
	double			one=0, two=0;
	long task;


	if (MLGetNext(stdlink) == MLTKINT)
		MLGetLongInteger(stdlink,&task);
	else
		bigerror();

	Job& job = persistJobs.getJob((TaskHandle) task);
//	sendmessage("in runtask taskHandle is : ", job.getTaskHandle(),job.getoptL("messagelevel"));
//	sendmessage("Here are the options: \\\\n", job.getOptions().toString(),job.getoptL("messagelevel"));


	MLFlush(stdlink);
	if (!MLReady(stdlink))
	{						// only one argument so input task
		job.continueTask();
		return;
	}
	// its a one or two channel output job
	switch (MLGetNext(stdlink))
	{
	case MLTKINT:
	case MLTKREAL:
		MLGetReal(stdlink,&one);
		job.continueTask(one);
		return;
	case MLTKFUNC:
		MLGetFunction(stdlink,&ctemp,&n1);
		string stemp = ctemp;
		MLDisownSymbol(stdlink,ctemp);	// direct comparison fails?
		if (stemp != "List" || n1 != 2)
			bigerror();
		MLGetReal(stdlink,&one);
		MLGetReal(stdlink,&two);
		job.continueTask(one,two);
		return;
	}
//	sendmessage("in runtask taskHandle is : ", job.getTaskHandle(),job.getoptL("messagelevel"));
//	sendmessage("Here are the options: \\\\n", job.getOptions().toString(),job.getoptL("messagelevel"));
	return;
}
void getvoltagessoftwaretrigcpp(void)
{
	SoftTrigJob stj("software triggering",persistJobs);
	stj.startTask(stj.getTaskHandle());
	sendmessage("entering wait for trigger","!",5);
	stj.waitForTrigger();
	if (MLAbort)
	{
		DAQmxClearTask(stj.getTaskHandle());
//		delete [] pTestingArray;  // posttrigger reuses testing array space
//		delete [] pPreTrigArray;
		MLNewPacket(stdlink);
		MLPutFunction(stdlink,"Abort",0);
		return;
	}
	stj.readPreTrigger();
	stj.readPostTrigger();
	stj.sendToMathematica();
	return;
}
void getvoltagesdigitaltrigcpp(void)
{
	DigitalTrigJob dtj("Digital Hardware triggering", persistJobs);
	dtj.startTask(dtj.getTaskHandle());
	dtj.isDoneNoError();				// should work done until samples are read.
	if (!MLAbort)
	{
		dtj.readData();
		dtj.sendToMathematica();
	}
	return;
}

void getvoltagescpp(void)
{
	option optTemp;
	optTemp.fromMathematica(stdlink);
	Job* jobptr = 0;
	if (optTemp.getL("samples") == 1)
		jobptr = new InputJob("repeat",persistJobs,optTemp);
	else
		jobptr = new BufferedInputJob("buffered repeat",persistJobs,optTemp);

	if (optTemp.getL("setuponly"))
	{
		if(optTemp.getL("samples") == 1)
		{
			int iErr = jobptr->startTask(jobptr->getTaskHandle());		// startahead of time for efficiency for static variable is run task or in persistjob
			checkerr(iErr, jobptr->getoptL("messagelevel"));
		}
		if (optTemp.getL("samples") != 1)
		{
//			; // intentionally blank start task in continue job to allow settransfermech
//			int iErr =DAQmxSetSampTimingType(jobptr->getTaskHandle(),DAQmx_Val_OnDemand);		// on demand is faster
//			checkerr(iErr, jobptr->getoptL("messagelevel"));
		}
		Job& pj = persistJobs.changeToPersist(*jobptr, 1);	// 1 =run continuous? Note: ij should not be used after this point
		MLPutLongInteger(stdlink,(long) pj.getTaskHandle());
	}
	else
	{
		if (optTemp.getL("samples") == 1)
		{
		jobptr->startTask(jobptr->getTaskHandle());		// run once immediately
		jobptr->continueTask();
		}
		else
			jobptr->continueTask();
	}
	delete jobptr;
	return;
}

int cleartask(int task)
{
	// can't be part of job but ideally would have access to message level
	int32		iErr=0;
	TaskHandle taskHandle = (TaskHandle) task;
	persistJobs.clearJob(taskHandle);
//	senderr(iErr);
	return 0;
}

OutputJob* CreateOutputJob(persistJobHolder& pj, option& opt)  // const references?
{
	if (opt.getL("samples") == 1)
		return new OutputJob("Out1 sample on" , pj, opt);
	else
		return new BufferedOutputJob("Output multiple samples", pj, opt);
}

void OutputWaveformDispatch(OutputJob* jptr, persistJobHolder& pjh, option& opt)
{
	if (jptr->getoptL("samplemode") == DAQmx_Val_ContSamps)
	{
		sendmessage("continuous output should be happening","!",5);
		Job& pj = pjh.changeToPersist(*jptr, 1);		// must use clear task
		pj.startTask(pj.getTaskHandle());
		MLPutLongInteger(stdlink, (long) pj.getTaskHandle());
		delete jptr;
		return;
	}

	if (jptr->getoptL("setuponly") == 1 && jptr->getoptL("samples") == 1)
	{	// do setuponly
		Job& pj = pjh.changeToPersist(*jptr, 1);		// must use clear task
		pj.startTask(pj.getTaskHandle());
		MLPutLongInteger(stdlink, (long) pj.getTaskHandle());
	}
	if(jptr->getoptL("setuponly")== 0 && jptr->getoptL("samples") == 1)
	{
		jptr->startTask(jptr->getTaskHandle());
		if (jptr->getoptL("numchannels") == 1)
			jptr->continueTask(jptr->getoptDArray("waveform")[0]);
		if (jptr->getoptL("numchannels")== 2)
			jptr->continueTask(jptr->getoptDArray("waveform")[0], jptr->getoptDArray("waveform")[1]);
	}
	if(jptr->getoptL("setuponly")==0 && jptr->getoptL("samples") != 1)
	{
		Job& pj = pjh.changeToPersist(*jptr,0);		// stop when task is done
		pj.startTask(pj.getTaskHandle());
		pj.sendToMathematica();
	}
	if(jptr->getoptL("setuponly")==1 && jptr->getoptL("samples") != 1)
	{
		Job& pj = pjh.changeToPersist(*jptr,1);		// must use clear
		pj.sendToMathematica();
	}
	delete jptr;
	return;
}
void outputwaveformcpp(void)
{
	option optTemp;
	optTemp.fromMathematica(stdlink);
	OutputJob* jobptr = CreateOutputJob(persistJobs, optTemp);   // const references ?
	OutputWaveformDispatch(jobptr, persistJobs, optTemp);   // polymorphic factor function // const references ?

	//~ if (optTemp.getL("samples") == 1)
		//~ jobptr = new OutputJob("Output one sample", persistJobs, optTemp);
	//~ else
		//~ jobptr = new BufferedOutputJob("Output multiple samples", persistJobs, optTemp);

//	message("Here is message", 34, jobptr); //doesn't work with mingw

	//~ if (jobptr->getoptL("samplemode") == DAQmx_Val_ContSamps)
	//~ {
		//~ sendmessage("continuous output should be happening","!",5);
		//~ Job& pj = persistJobs.changeToPersist(*jobptr, 1);		// must use clear task
		//~ pj.startTask(pj.getTaskHandle());
		//~ MLPutLongInteger(stdlink,pj.getTaskHandle());
		//~ delete jobptr;
		//~ return;
	//~ }

	//~ if (jobptr->getoptL("setuponly") == 1 && jobptr->getoptL("samples") == 1)
	//~ {	// do setuponly
		//~ Job& pj = persistJobs.changeToPersist(*jobptr, 1);		// must use clear task
		//~ pj.startTask(pj.getTaskHandle());
		//~ MLPutLongInteger(stdlink,pj.getTaskHandle());
	//~ }
	//~ if(jobptr->getoptL("setuponly")== 0 && jobptr->getoptL("samples") == 1)
	//~ {
//~ //		jobptr->dataStorage[0]=jobptr->getoptDArray("waveform")[0];		//run once
//~ //		jobptr->dataStorage[1]=jobptr->getoptDArray("waveform")[1];
		//~ jobptr->startTask(jobptr->getTaskHandle());
		//~ if (jobptr->getoptL("numchannels") == 1)
			//~ jobptr->continueTask(jobptr->getoptDArray("waveform")[0]);
		//~ if (jobptr->getoptL("numchannels")== 2)
			//~ jobptr->continueTask(jobptr->getoptDArray("waveform")[0], jobptr->getoptDArray("waveform")[0]);
	//~ }
	//~ if(jobptr->getoptL("setuponly")==0 && jobptr->getoptL("samples") != 1)
	//~ {
		//~ Job& pj = persistJobs.changeToPersist(*jobptr,0);		// stop when task is done
		//~ pj.startTask(pj.getTaskHandle());
		//~ pj.sendToMathematica();
	//~ }
	//~ if(jobptr->getoptL("setuponly")==1 && jobptr->getoptL("samples") != 1)
	//~ {
		//~ Job& pj = persistJobs.changeToPersist(*jobptr,1);		// must use clear
		//~ pj.sendToMathematica();
	//~ }

//	delete jobptr;		//should cleanup single nonpersitant task
	return;
/*********************************************************************
*    1. Create a task.
*    2. Create an Analog Output Voltage channel.
*    3. Define the update Rate for the Voltage generation. Additionally,
*       define the sample mode to be continuous.
*********************************************************************/
// channels_String, waverform_List, numchannels_Integer,numsampsperchan_Integer,
// frequency_Real, numperiods_Real, min_Real, max_Real,units_Integer,clock_String,
// activeedge_Integer, transfermech_Integer, samplemode_Integer, timeout_Real, messagelevel_Integer

	/* here is the undocumented calls to change Data Transfer method found in nidaq.h

	#define DAQmx_Val_DMA                                                     10054 // DMA
	#define DAQmx_Val_Interrupts                                              10204 // Interrupts
	#define DAQmx_Val_ProgrammedIO                                            10264 // Programmed I/O

	DAQmxGetAODataXferReqCond(TaskHandle taskHandle, const char channel[], int32 *data);
	DAQmxSetAODataXferMech(TaskHandle taskHandle, const char channel[], int32 data);
	DAQmxResetAODataXferMech(TaskHandle taskHandle, const char channel[]);
	*/

/*
	OutputJob oj("output test", persistJobs);
	Job& pj = persistJobs.changeToPersist(oj,0);		// stop when task is done
	pj.startTask(pj.getTaskHandle());
	pj.sendToMathematica();
*/

	return;
}
int outputwaveform(double *waveform,long waveformSize, double freq, double numPeriods = 1.0)
{
/*********************************************************************
*    1. Create a task.
*    2. Create an Analog Output Voltage channel.
*    3. Define the update Rate for the Voltage generation. Additionally,
*       define the sample mode to be continuous.
*********************************************************************/

	/* here is the undocumented calls to change Data Transfer method found in nidaq.h

	#define DAQmx_Val_DMA                                                     10054 // DMA
	#define DAQmx_Val_Interrupts                                              10204 // Interrupts
	#define DAQmx_Val_ProgrammedIO                                            10264 // Programmed I/O

	DAQmxGetAODataXferReqCond(TaskHandle taskHandle, const char channel[], int32 *data);
	DAQmxSetAODataXferMech(TaskHandle taskHandle, const char channel[], int32 data);
	DAQmxResetAODataXferMech(TaskHandle taskHandle, const char channel[]);
	*/

	TaskHandle  taskHandle=0;
	int32		iErr=0;
	char		errBuff[2048]={'\0'};

	iErr = DAQmxCreateTask("output_waveform",&taskHandle);
	senderr(iErr);

	char chan[] ="Dev1/ao0";
	double min = -2.00;
	double max = 2.00;
	iErr = DAQmxCreateAOVoltageChan(taskHandle,chan,"",min,max,DAQmx_Val_Volts,NULL);
	senderr(iErr);

	/* To use analog input and output at the same time change Data transfer method to
		interrupts for analog output, DMA faster but card only has one DMA channel*/
	iErr = DAQmxSetAODataXferMech(taskHandle, chan, DAQmx_Val_Interrupts);
	senderr(iErr);

	int32 sampleMode;
	int numSamples = waveformSize;
	if (numPeriods < 0.0)
		sampleMode = DAQmx_Val_ContSamps;
	else
	{
	sampleMode = DAQmx_Val_FiniteSamps;
	numSamples = waveformSize * numPeriods;
	}


	iErr = DAQmxCfgSampClkTiming(taskHandle,"",freq,DAQmx_Val_Rising,sampleMode,numSamples);
	senderr(iErr);

// Writes data from user allocated buffer.
	bool autoStart = false;
	double timeOut = 10.0;
	int32 written;
	iErr = DAQmxWriteAnalogF64(taskHandle,waveformSize,autoStart,
		timeOut,DAQmx_Val_GroupByChannel,waveform,&written,NULL);
	senderr(iErr);
// Starts the task.
	iErr=DAQmxStartTask(taskHandle);
	senderr(iErr);
// need to check for stop conditon if continuous output
//	clock_t		startTime;
	bool32      done=0;

	return (int) taskHandle;
}

//void getvoltage_trig(int numSamples,double sampleFreq,double triggerLevel)
//{
//	/*********************************************************************
//	*    1. Create a task.
//	*    2. Create an analog input voltage channel.
//	*    3. Set the rate for the sample clock. Additionally, define the
//	*       sample mode to be finite.
//	*    4. Define the parameters for an Analog Slope Start Trigger. (!! No analog trig this card !!)
//	*********************************************************************/
//	int32 iErr=0;
//	TaskHandle taskHandle;
//	DAQmxCreateTask ("triggered analog in", &taskHandle);
//	const char chan[] = "dev1/ai0";
//	int32 terminalConfig = DAQmx_Val_Diff;
//	float64 minVal = -10.0;
//	float64 maxVal= 10.0;
//	int32 units = DAQmx_Val_Volts;
//	const char triggerSource[] = "PFI0";
//	int32 triggerSlope = DAQmx_Val_RisingSlope;
//
//	int32 numSampsPerChan = numSamples;			//changed
//	float64 timeout = 10;				// time in seconds
//	bool32 fillMode = DAQmx_Val_GroupByChannel;
//	float64 readArray[10000];			// 10000 samples max!!
//	uInt32 arraySizeInSamps = numSamples;     // changed
//	int32 sampsPerChanRead=0;
//
//	iErr = DAQmxCreateAIVoltageChan(taskHandle,chan,"",terminalConfig,minVal,maxVal,
//		DAQmx_Val_Volts,NULL);
//	iErr=0;
//	senderr(iErr);
//
//	iErr = DAQmxCfgSampClkTiming(taskHandle,"",sampleFreq,DAQmx_Val_Rising,
//		DAQmx_Val_FiniteSamps,numSamples);
//	senderr(iErr);
//
//	iErr = DAQmxCfgDigEdgeStartTrig(taskHandle,triggerSource,triggerSlope);		// digital triggering
//	senderr(iErr);
//
//	// Starts the task.
//	iErr = DAQmxStartTask(taskHandle);
//
//	// Reads data into user allocated buffer.
//	// Recommended parameters:
//	//   bufferSize     = 1000
//
////	outputvoltage(5.0);
////	outputvoltage(0.0);
//
//	iErr = DAQmxReadAnalogF64(taskHandle,numSampsPerChan,timeout,fillMode,
//		readArray,arraySizeInSamps,&sampsPerChanRead,NULL);
//
//	MLPutRealList(stdlink,readArray,numSamples);
//	// Stops the task.
//	iErr = DAQmxStopTask(taskHandle);
//	iErr = DAQmxClearTask(taskHandle);
//}

int outputvoltage(float64 volts)
	{
		int32 iErr=0;
		TaskHandle taskHandle;
		DAQmxCreateTask ("get some points", &taskHandle);
		const char chan[]="dev1/ao0";
		float64 min = -10.0;
		float64 max = 10.0;
		MLEvaluateString(stdlink,"Print[\"hello from create output channel\"]");
		iErr = DAQmxCreateAOVoltageChan(taskHandle,chan,"",min,max,DAQmx_Val_Volts,"");
		DAQmxWriteAnalogF64(taskHandle,1,1,10.0,DAQmx_Val_GroupByChannel,&volts,NULL,NULL);
		DAQmxStartTask(taskHandle);
		DAQmxStopTask(taskHandle);
		DAQmxClearTask(taskHandle);
		return iErr;
	}


void getvoltages(int numSamples, float64 sampleFreq)
		{
		// create the task handle that other functions need
		int32 iErr;
		TaskHandle taskHandle;
		iErr = DAQmxCreateTask ("get some points", &taskHandle);
		senderr(iErr);
		//  configue the channels
		const char physicalChannel[]="dev1/ai0";
		const char nameToAssignToChannel[]="in0";
		int32 terminalConfig=DAQmx_Val_Diff;
		float64 minVal = -10.0;
		float64 maxVal= 10.0;
		int32 units = DAQmx_Val_Volts;
		const char customScaleName[]="";

		iErr=DAQmxCreateAIVoltageChan (taskHandle,physicalChannel,nameToAssignToChannel, \
			terminalConfig,minVal,maxVal,units,NULL);
		senderr(iErr);
//		DAQmxGetErrorString (iErr, errmsg, 2000);
//		std::cout << errmsg <<std::endl;

		int32 numSampsPerChan = numSamples;			//changed
		float64 timeout = 10;				// time in seconds
		bool32 fillMode = DAQmx_Val_GroupByChannel;
		float64 readArray[10000];			// 10000 samples max!!
		uInt32 arraySizeInSamps = numSamples;     // changed
		int32 sampsPerChanRead=0;


		const char source[]="OnboardClock";
		float64 rate = sampleFreq;
		int32 activeEdge = DAQmx_Val_Rising;
		int32 sampleMode = DAQmx_Val_FiniteSamps;
		uInt64 sampsPerChanToAcquire = numSamples;   //changed

		iErr = DAQmxCfgSampClkTiming (taskHandle,source,rate, activeEdge,sampleMode, \
			sampsPerChanToAcquire);
		senderr(iErr);
//		DAQmxGetErrorString (iErr, errmsg, 2000);
//		std::cout << errmsg <<std::endl;

		iErr = DAQmxReadAnalogF64 (taskHandle, numSampsPerChan, timeout, fillMode, \
			readArray, arraySizeInSamps, &sampsPerChanRead, NULL);
		senderr(iErr);

		MLPutRealList(stdlink,readArray,numSamples);

		iErr = DAQmxClearTask (taskHandle);
		senderr(iErr);
		return;
	}

#if MACINTOSH_MATHLINK

int main( int argc, char* argv[])
{
	/* Due to a bug in some standard C libraries that have shipped with
	 * MPW, zero is passed to MLMain below.  (If you build this program
	 * as an MPW tool, you can change the zero to argc.)
	 */
	argc = argc; /* suppress warning */
	return MLMain( 0, argv);
}

#elif WINDOWS_MATHLINK

#if __BORLANDC__
#pragma argsused
#endif

int PASCAL WinMain( HINSTANCE hinstCurrent, HINSTANCE hinstPrevious, LPSTR lpszCmdLine, int nCmdShow)
{
	char  buff[512];
	char FAR * buff_start = buff;
	char FAR * argv[32];
	char FAR * FAR * argv_end = argv + 32;

	hinstPrevious = hinstPrevious; /* suppress warning */

	if( !MLInitializeIcon( hinstCurrent, nCmdShow)) return 1;
	MLScanString( argv, &argv_end, &lpszCmdLine, &buff_start);
	return MLMain( argv_end - argv, argv);
}

#else

int main(argc, argv)
	int argc; char* argv[];
{
	return MLMain(argc, argv);
}

#endif
